-module(test_executor_supervisor).
-include("definitions.hrl").

-export([start/2, stop/0, init/1, stop_executors/0]).
-export([event_loop/3]).

-export([
  get_test_executor/0,
  shift/3,
  reborn_me/1,
  i_am_dead/0,
  i_am_reborned/0]).


%% начать тестирование
-spec start/2 :: (Devices, Tests) -> {ok, testsRunning}
  when
  Devices :: [device()],
  Tests :: dict:dict(device_id(), test_id()).

start(Devices, Tests) ->
  stop(),
  init(Devices),
  ok = fresh_tests_and_devices(Tests, Devices),
  startExecutors(),
%%   ok = test_executor:soft_stop(testExecutor1),
%%   ok = test_executor:soft_stop(testExecutor2),
%%   receive
%%     dead -> ok
%%   after 50 -> ok
%%   end,
%%   receive
%%     dead -> ok
%%   after 50 -> ok
%%   end,
  receive
    executors_started ->
      {ok, testsRunning}
  end.

startExecutors() ->
  global:whereis_name(testExecutorSupervisor) ! {start_default, self()}.

fresh_tests_and_devices(Tests, Devices) ->
  global:whereis_name(testExecutorSupervisor) ! {fresh_tests, Tests, Devices, self()},
  receive
    {got_tests, ok} -> ok
  end
.

i_am_dead() -> global:whereis_name(testExecutorSupervisor) ! dead.

i_am_reborned() ->
  global:whereis_name(testExecutorSupervisor) ! {ok, testsRunning}.

%% первоначальный запуск
-spec init/1 :: ([device()]) -> true.
init(Devices) ->
  State = {t1_works, reborned},
  Tests = dict:new(),
  Params = [State, Devices, Tests],

  yes = global:register_name(testExecutorSupervisor, spawn(test_executor_supervisor, event_loop, Params)),
%%   true = register(testExecutorSupervisor, spawn(test_executor_supervisor, event_loop, Params)),
  true.


-spec stop/0 :: () -> ok.
stop() ->
  ok = test_executor:stop(testExecutor1),
  ok = test_executor:stop(testExecutor2),
%%   P = global:whereis_name(testExecutorSupervisor),
  case global:whereis_name(testExecutorSupervisor) of
    undefined ->
      ok;
    Pid when is_pid(Pid) ->
      Pid ! {stop, self()},
      storage_supervisor:flush(),
      storage_supervisor:clear(),
      receive
        testExecutorSupervisorStopped ->
          ok
      end
  end.

stop_executors() ->
  ok = test_executor:stop(testExecutor1),
  ok = test_executor:stop(testExecutor2).


-spec shift/3 :: (Workers, Pids, TestingState) -> ok
  when
  Workers :: dict:dict(device_id(), pid()),
  Pids :: dict:dict(pid(), device_id()),
  TestingState :: #testingState{}
.
shift(Workers, Pids, TestingState) ->
  global:whereis_name(testExecutorSupervisor) ! {shift, Workers, Pids, TestingState},
  ok.

-spec reborn_me/1 :: (atom()) -> {reborn, atom()}.
reborn_me(Name) ->
  global:whereis_name(testExecutorSupervisor) ! {reborn, Name}.


-spec event_loop/3 :: (State, Devices, Tests) -> ok
  when
  State :: {t1_works | t2_works, reborned | not_reborned},
  Devices :: [device()],
  Tests :: dict:dict(device_id(), test_id()).

event_loop(State, Devices, Tests) ->
  receive
    {stop, From} ->
%%       unregister(testExecutorSupervisor),
      From ! testExecutorSupervisorStopped,
      ok;

    {start_default, From} when is_pid(From) ->
      ok = start_default(Devices, Tests),
      From ! executors_started,
      event_loop(State, Devices, Tests);

    dead ->
      {Works, _} = State,
      NewState = {Works, not_reborned},
      event_loop(NewState, Devices, Tests);

    {'DOWN', _, process, _, Reason} ->
      io:format("down from sup ~p~n", [Reason]),
      event_loop(State, Devices, Tests);

    {fresh_tests, TestsNew, DevicesNew, From} when is_pid(From) ->
      From ! {got_tests, ok},
      event_loop(State, DevicesNew, TestsNew);

    {reborn, _} ->
      NewState = reborn(State, Devices, Tests),
      event_loop(NewState, Devices, Tests);

    {shift, WorkersNew, PidsNew, TestingStateNew} ->
      NewState = shift_state(State, WorkersNew, PidsNew, Devices, Tests, TestingStateNew),
      event_loop(NewState, Devices, Tests);

    {get_atom, From} when is_pid(From) ->
      Atom = case State of
               {t1_works, _} -> testExecutor1;
               {t2_works, _} -> testExecutor2
             end,
      From ! {your_atom, Atom},
      event_loop(State, Devices, Tests)
  end.

-spec get_test_executor/0 :: () -> atom().
get_test_executor() ->
  try global:whereis_name(testExecutorSupervisor) of
    undefined ->
      error("test executor supervisor is undefined");
    Pid when is_pid(Pid) ->
      Pid ! {get_atom, self()},
      receive

        {your_atom, Atom} -> Atom
      end
  catch
    error:R ->
      io:format("testExecutor is already finished~n"),
      io:format(R),
      io:nl()
  end.

-spec shift_state/6 :: (State, WorkersNew, PidsNew, Devices, Tests, #testingState{})
      -> {t1_works | t2_works, reborned | not_reborned}
  when
  State :: {t1_works | t2_works, reborned | not_reborned},
  WorkersNew :: dict:dict(device_id(), pid()),
  PidsNew :: dict:dict(pid(), device_id()), %Pids[Pid]->DeviceId
  Devices :: [device()],
  Tests :: dict:dict(device_id(), test_id()).

shift_state(State, WorkersNew, PidsNew, Devices, Tests, TestingState) ->
%%   {A, B} = State,
%%   io:format("{~p, ~p} ~n", [A, B]),
  case State of
    {t1_works, not_reborned} ->
      receive
        {reborn, testExecutor2} ->
          _ = reborn(State, Devices, Tests)
      end,
      testExecutor2 ! {start_monitor_workers, WorkersNew, PidsNew, TestingState, testExecutor1},
      {t2_works, not_reborned};


    {t1_works, reborned} ->
      testExecutor2 ! {start_monitor_workers, WorkersNew, PidsNew, TestingState, testExecutor1},
      {t2_works, not_reborned};


    {t2_works, not_reborned} ->
      receive
        {reborn, testExecutor1} ->
          _ = reborn(State, Devices, Tests)
      end,
      testExecutor1 ! {start_monitor_workers, WorkersNew, PidsNew, TestingState, testExecutor2},
      {t1_works, not_reborned};


    {t2_works, reborned} ->
      testExecutor1 ! {start_monitor_workers, WorkersNew, PidsNew, TestingState, testExecutor2},
      {t1_works, not_reborned}
  end.


-spec reborn/3 :: (State, Devices, Tests) -> NewState
  when
  State :: {t1_works | t2_works, reborned | not_reborned},
  Devices :: [device()],
  Tests :: dict:dict(device_id(), test_id()),
  NewState :: {t1_works | t2_works, reborned | not_reborned}.

reborn(State, Devices, Tests) ->
  Atom = case State of
           {t1_works, _} -> testExecutor2;
           {t2_works, _} -> testExecutor1
         end,
  test_executor:start(Devices, Tests, Atom, stays),
  receive
    {ok, testsRunning} -> ok
  end,
  {element(1, State), reborned}.

-spec start_default/2 :: (Devices, Tests) -> ok
  when
  Devices :: [device()],
  Tests :: dict:dict(device_id(), test_id()).

start_default(Devices, Tests) ->
  ok = test_executor:start(Devices, Tests, testExecutor1, works),
  ok = test_executor:start(Devices, Tests, testExecutor2, stays).
